﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Collections.Concurrent;
using System.Threading;
using MMessaging.MessagingEx.Common;
using LogX.Logging;
using MMessaging.MessagingEx.Logger;
using TUtils.BasicHelper.Logging;

namespace MMessaging.MessagingEx.Queues
{
	/// <summary>
	/// Repräsentiert eine Messagequeue für den einfachen Transport von priorisierten Messages innerhalb eines 
	/// Betriebssystem-Prozesses. Simplex-Verfahren == Unidirektional.
	/// </summary>
	/// <remarks>
	/// Messages mit einer höheren Priorität (z.B. Priorität 1 ist höher als Priorität 2)
	/// überholen andere Messages mit kleinerer Priorität.
	/// </remarks>
	/// <typeparam name="MESSAGETYPE">
	/// should implement interface IMPrioMessage
	/// </typeparam>
	public class MChannelInterThreadPrio<MESSAGETYPE> :
		IMChannel<MESSAGETYPE>
		where MESSAGETYPE : class
	{
		#region private 

		private const ushort DEFAULT_PRIO = 10;

		/// <summary>
		/// map prio -> queue of messages
		/// </summary>
		private Dictionary<ushort, List<MESSAGETYPE>> _messageQueueMap = new Dictionary<ushort, List<MESSAGETYPE>>();
		private object _syncObj = new object();

		private ManualResetEvent _signal = new ManualResetEvent(false);

		private MESSAGETYPE TryGetMessage()
		{
			foreach (var prio in _messageQueueMap.Keys.OrderBy(key=>key))
			{
				var prioList = _messageQueueMap[prio];
				var msg = prioList.FirstOrDefault();

				if (msg != null)
				{
					prioList.RemoveAt(0);
					return msg;
				}
			}

			return null;
		}

		#endregion 

		#region public

		/// <summary>
		/// Konstruktor
		/// </summary>
		/// <param name="name">
		/// Logging-Name des Channels.
		/// </param>
		public MChannelInterThreadPrio(string name)
		{
			Name = name;
		}

		/// <summary>
		/// Blockiert bis eine Message bereit steht oder der übergebene Timeout ausläuft.
		/// </summary>
		/// <param name="timeout"></param>
		/// <returns>
		/// null, wenn der Timeout ausgelaufen ist.
		/// </returns>
		public MESSAGETYPE WaitForMessage(int timeout)
		{
			string name = Name + "_" + this.GetHashCode();
			lock (_syncObj)
			{
				var msg = TryGetMessage();
				if (msg != null)
				{
					// System.Diagnostics.Debug.WriteLine("GTZZGX channel: " + name + "; WaitForMessage() returns " + msg ?? "null");
					return msg;
				}

				_signal.Reset();
			}

			if (!_signal.WaitOne(timeout))
			{
				// System.Diagnostics.Debug.WriteLine("GTZZGX channel: " + name + "; WaitForMessage() returns null");
				return null;
			}

			lock (_syncObj)
			{
				var msg = TryGetMessage();
				// System.Diagnostics.Debug.WriteLine("GTZZGX channel: " + name + "; WaitForMessage() returns " + msg ?? "null");
				return msg;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="message"></param>
		public void PostMessage(MESSAGETYPE message)
		{
#			if DEBUG
			bool stepover = 1 > 0;
			if (!stepover)
			{
				// #####################################################################
				// Die Verarbeitung der Message kann im Debugger weiterverfolgt werden,
				// obwohl sie gleich den Thread wechselt. Hierzu einfach den Cursor auf
				// die direkt folgende Codezeile setzen und CTRL+SHIFT+F10 drücken und anschließend 
				// F5 drücken.
				// Beachten, dass Breakpoints an den Codezeilen mit dem Tag ZVDZJHFESJ
				// gesetzt sein müssen.
				// #####################################################################
				var debuggableMsg = message as IMDebugableMessage;
				// #####################################################################

				if (debuggableMsg != null)
					debuggableMsg.Break = true;
			}
#			endif

			lock (_syncObj)
			{
				if (message == null)
					throw new ApplicationException("djfh2894fz3fiowioefh");
				// System.Diagnostics.Debug.WriteLine("GTZZGX channel: " + Name + "_" + this.GetHashCode() + "; PostMessage() entered " + message ?? "null");
				ushort prio = DEFAULT_PRIO;
				var prioMessage = message as IMPrioMessage;
				if ( prioMessage != null )
					prio = prioMessage.Priority;

				List<MESSAGETYPE> prioList;
				if (!_messageQueueMap.TryGetValue(prio, out prioList))
				{
					prioList = new List<MESSAGETYPE>();
					_messageQueueMap[prio] = prioList;
				}

				prioList.Add(message);
			}


			_signal.Set();
		}

		/// <summary>
		/// Signals when there is a message in the queue
		/// </summary>
		public WaitHandle[] WaitSignals
		{
			get 
			{
				return new WaitHandle[] { _signal };
			}
		}

		public string Name { get; set; }


		#endregion
	}
}
